/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.wisii.wisedoc.area;


import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.wisii.wisedoc.area.inline.QianZhangArea;
import com.wisii.wisedoc.document.Constants;
import com.wisii.wisedoc.document.attribute.SimplePageMaster;
import com.wisii.wisedoc.log.LogUtil;



/**
 * Page viewport that specifies the viewport area and holds the page contents. This is the top level object for a page
 * and remains valid for the life of the document and the area tree. This object may be used as a key to reference a page.
 * This is the level that creates the page. The page (reference area) is then rendered inside the page object
 */
public class PageViewport extends AreaTreeObject implements Resolvable, Cloneable, Serializable
{
    /* 【添加：Start】 by 李晓光 2008-03-02 */
    private String mediaUsage;
    private List<BlockViewport> containers = null;
    private List<QianZhangArea> qianzhangareas = null;
    public void addBlockViewport(BlockViewport block){
    	if(containers == null)
    		containers = new ArrayList<BlockViewport>();
    	containers.add(block);
    }
    public List<BlockViewport> getContainers(){
    	return containers;
    }

	public void addQianZhangArea(QianZhangArea qianzhanArea)
	{
		if (qianzhangareas == null)
		{
			qianzhangareas = new ArrayList<QianZhangArea>();
		}
		qianzhangareas.add(qianzhanArea);
	}
    public List<QianZhangArea> getQianZhangAreas(){
    	return qianzhangareas;
    }
    /* 【添加：End】 by 李晓光 2008-03-02 */
    private Page page;
    private Rectangle2D viewArea;
    private String simplePageMasterName;
    /* 添加：by 李晓光2009-12-4  目的：为了实时更新界面*/
    private SimplePageMaster spm;
    public SimplePageMaster getSimplePageMaster(){
    	return spm;
    }
    /* 添加：by 李晓光2009-12-4  */
    /** Unique key to identify the page. pageNumberString and pageIndex are both no option for this.     */
    private String pageKey;

    private int pageNumber = -1;
    private String pageNumberString = null;
    private int pageIndex = -1; //-1 = undetermined
    private boolean blank;

    private transient PageSequence pageSequence;

    // list of id references and the rectangle on the page private Map idReferences = null;
    // this keeps a list of currently unresolved areas or extensions once an idref is resolved it is removed
    // when this is empty the page can be rendered
    private Map unresolvedIDRefs = new java.util.HashMap();
    private Map pendingResolved = null;

    // hashmap of markers for this page, start and end are added by the fo that contains the markers
    private Map markerFirstStart = null;
    private Map markerLastStart = null;
    private Map markerFirstAny = null;
    private Map markerLastEnd = null;
    private Map markerLastAny = null;

    //Arbitrary attachments to the page from extensions that need to pass information down to the renderers.
    private List extensionAttachments = null;

    /**
     * Create a page viewport.
     * @param spm SimplePageMaster indicating the page and region dimensions
     * @param pageNumber the page number
     * @param pageStr String representation of the page number
     * @param blank true if this is a blank page
     */
    public PageViewport(SimplePageMaster spm, int pageNumber, String pageStr, boolean blank)
    {
    	/* 添加：by 李晓光2009-12-4  */
    	this.spm = spm;
    	/* 添加：by 李晓光2009-12-4  */
        /* 【添加：Start】 by 李晓光 2008-03-02 */
        mediaUsage = spm.getMediaUsage();
        /* 【添加：End】 by 李晓光 2008-03-02 */
        this.simplePageMasterName = spm.getMasterName();
        this.extensionAttachments = spm.getExtensionAttachments();
        this.blank = blank;
        int pageWidth = spm.getPageWidth().getValue();
        int pageHeight = spm.getPageHeight().getValue();
        this.pageNumber = pageNumber;
        this.pageNumberString = pageStr;
        this.viewArea = new Rectangle(0, 0, pageWidth, pageHeight);
        this.page = new Page(spm);
        /* 【添加：START】 by 李晓光 2008-10-17 */
        this.page.setParent(this);
        setAreaKind(AreaKind.VIEWPORT);
        /* 【添加：END】by 李晓光 2008-10-17  */
        createSpan(false);
    }

    /* 【添加：Start】 by 李晓光 2008-03-02 */
    /**
     * 获得FO设置的打印机相关信息【指定打印机、打印机的Port（进纸口）】
     * @return String
     */
    public String getMediaUsage()
    {
        return mediaUsage;
    }

    /* 【添加：End】 by 李晓光 2008-03-02 */
    /**
     * Copy constructor.
     * @param original the original PageViewport to copy from
     */
    public PageViewport(PageViewport original)
    {
        if(original.extensionAttachments != null)
        {
            this.extensionAttachments = new java.util.ArrayList(original.extensionAttachments);
        }
        this.pageNumber = original.pageNumber;
        this.pageNumberString = original.pageNumberString;
        this.page = (Page)original.page.clone();
        this.viewArea = (Rectangle2D)original.viewArea.clone();
        this.simplePageMasterName = original.simplePageMasterName;
        this.blank = original.blank;
        /* 【添加：START】 by 李晓光 2008-11-13 */
        this.setAreaKind(original.getAreaKind());
        /* 【添加：END】 by 李晓光 2008-11-13 */
        /* 添加：by 李晓光2009-12-4  */
    	this.spm = original.getSimplePageMaster();
    	/* 添加：by 李晓光2009-12-4  */
    }

    /**
     * Constructor used by the area tree parser.
     * @param viewArea the view area
     * @param pageNumber the page number
     * @param pageStr String representation of the page number
     * @param simplePageMasterName name of the original simple-page-master that generated this page
     * @param blank true if this is a blank page
     */
    public PageViewport(Rectangle2D viewArea, int pageNumber, String pageStr, String simplePageMasterName, boolean blank)
    {
        this.viewArea = viewArea;
        this.pageNumber = pageNumber;
        this.pageNumberString = pageStr;
        this.simplePageMasterName = simplePageMasterName;
        this.blank = blank;
    }

    /**
     * Sets the page sequence this page belongs to
     * @param seq the page sequence
     */
    public void setPageSequence(PageSequence seq)
    {
        this.pageSequence = seq;
    }

    /** @return the page sequence this page belongs to */
    public PageSequence getPageSequence()
    {
        return this.pageSequence;
    }

    /**
     * Get the view area rectangle of this viewport.
     * @return the rectangle for this viewport
     */
    public Rectangle2D getViewArea()
    {
        return viewArea;
    }
    /*【添加：START】 by 李晓光 2008-11-13 */
    public Rectangle2D getViewport(){
    	return viewArea;
    }
    /*【添加：END】 by 李晓光 2008-11-13 */
    /**
     * Get the page reference area with the contents.
     * @return the page reference area
     */
    public Page getPage()
    {
        return page;
    }

    /**
     * Sets the page object for this PageViewport.
     * @param page the page
     */
    public void setPage(Page page)
    {
        this.page = page;
    }

    /**
     * Get the page number of this page.
     * @return the integer value that represents this page
     */
    public int getPageNumber()
    {
        return pageNumber;
    }

    /**
     * Get the page number of this page.
     * @return the string that represents this page
     */
    public String getPageNumberString()
    {
        return pageNumberString;
    }

    /**
     * Sets the page index of the page in this rendering run.
     * (This is not the same as the page number!)
     * @param index the page index (zero-based), -1 if it is undetermined
     */
    public void setPageIndex(int index)
    {
        this.pageIndex = index;
    }

    /** @return the overall page index of the page in this rendering run (zero-based, -1 if it is undetermined).     */
    public int getPageIndex()
    {
        return this.pageIndex;
    }

    /**
     * Sets the unique key for this PageViewport that will be used to reference this page.
     * @param key the unique key.
     */
    public void setKey(String key)
    {
        this.pageKey = key;
    }

    /**
     * Get the key for this page viewport. This is used so that a serializable key can be used to lookup the page or
     * some other reference.
     * @return a unique page viewport key for this area tree
     */
    public String getKey()
    {
        if(this.pageKey == null)
        {
            throw new IllegalStateException("PageViewpor t中没有设置页 key: " + toString());
        }
        return this.pageKey;
    }

    /**
     * Add an idref to this page. All idrefs found for child areas of this PageViewport are added to unresolvedIDRefs,
     * for subsequent resolution by AreaTreeHandler calls to this object's resolveIDRef().
     * @param idref the idref
     * @param res the child element of this page that needs this idref resolved
     */
    public void addUnresolvedIDRef(String idref, Resolvable res)
    {
        if(unresolvedIDRefs == null)
        {
            unresolvedIDRefs = new HashMap();
        }
        List list = (List)unresolvedIDRefs.get(idref);
        if(list == null)
        {
            list = new ArrayList();
            unresolvedIDRefs.put(idref, list);
        }
        list.add(res);
    }

    /**
     * Check if this page has been fully resolved.
     * @return true if the page is resolved and can be rendered
     */
    public boolean isResolved()
    {
        return unresolvedIDRefs == null || unresolvedIDRefs.size() == 0;
    }

    /**
     * Get the unresolved idrefs for this page.
     * @return String array of idref's that still have not been resolved
     */
    public String[] getIDRefs()
    {
        return(unresolvedIDRefs == null) ? null : (String[])unresolvedIDRefs.keySet().toArray(new String[]
                                                                                              {});
    }

    /** @see com.wisii.wisedoc.area.Resolvable#resolveIDRef(String, List)     */
    public void resolveIDRef(String id, List pages)
    {
        if(page == null)
        {
            if(pendingResolved == null)
            {
                pendingResolved = new HashMap();
            }
            pendingResolved.put(id, pages);
        }
        else
        {
            if(unresolvedIDRefs != null)
            {
                List todo = (List)unresolvedIDRefs.get(id);
                if(todo != null)
                {
                    for(int count = 0; count < todo.size(); count++)
                    {
                        Resolvable res = (Resolvable)todo.get(count);
                        res.resolveIDRef(id, pages);
                    }
                }
            }
        }
        if(unresolvedIDRefs != null && pages != null)
        {
            unresolvedIDRefs.remove(id);
            if(unresolvedIDRefs.isEmpty())
            {
                unresolvedIDRefs = null;
            }
        }
    }

    /**
     * Add the markers for this page. Only the required markers are kept.
     * For "first-starting-within-page" it adds the markers that are starting only if the marker class name is not already added.
     * For "first-including-carryover" it adds any starting marker if the marker class name is not already added.
     * For "last-starting-within-page" it adds all marks that are starting, replacing earlier markers.
     * For "last-ending-within-page" it adds all markers that are ending, replacing earlier markers.
     * Should this logic be placed in the Page layout manager.
     * @param marks the map of markers to add
     * @param starting if the area being added is starting or ending
     * @param isfirst if the area being added has is-first trait
     * @param islast if the area being added has is-last trait
     */
    public void addMarkers(Map marks, boolean starting, boolean isfirst, boolean islast)
    {
        if(marks == null)
        {
            return;
        }
        if(LogUtil.canDebug())
        {
        	LogUtil.debug("--" + marks.keySet() + ": " + (starting ? "starting" : "ending") + (isfirst ? ", first" : "")
                      + (islast ? ", last" : ""));
        }

        // at the start of the area, register is-first and any areas
        if(starting)
        {
            if(isfirst)
            {
                if(markerFirstStart == null)
                {
                    markerFirstStart = new HashMap();
                }
                if(markerFirstAny == null)
                {
                    markerFirstAny = new HashMap();
                }
                // first on page: only put in new values, leave current
                for(Iterator iter = marks.keySet().iterator(); iter.hasNext(); )
                {
                    Object key = iter.next();
                    if(!markerFirstStart.containsKey(key))
                    {
                        markerFirstStart.put(key, marks.get(key));
                        if(LogUtil.canDebug())
                        {
                        	LogUtil.debug("page " + pageNumberString + ": " + "Adding marker " + key + " to FirstStart");
                        }
                    }
                    if(!markerFirstAny.containsKey(key))
                    {
                        markerFirstAny.put(key, marks.get(key));
                        if(LogUtil.canDebug())
                        {
                        	LogUtil.debug("page " + pageNumberString + ": " + "Adding marker " + key + " to FirstAny");
                        }
                    }
                }
                if(markerLastStart == null)
                {
                    markerLastStart = new HashMap();
                }
                // last on page: replace all
                markerLastStart.putAll(marks);
                if(LogUtil.canDebug())
                {
                	LogUtil.debug("page " + pageNumberString + ": " + "Adding all markers to LastStart");
                }
            }
            else
            {
                if(markerFirstAny == null)
                {
                    markerFirstAny = new HashMap();
                }
                // first on page: only put in new values, leave current
                for(Iterator iter = marks.keySet().iterator(); iter.hasNext(); )
                {
                    Object key = iter.next();
                    if(!markerFirstAny.containsKey(key))
                    {
                        markerFirstAny.put(key, marks.get(key));
                        if(LogUtil.canDebug())
                        {
                        	LogUtil.debug("page " + pageNumberString + ": " + "Adding marker " + key + " to FirstAny");
                        }
                    }
                }
            }
        }
        else
        {
            // at the end of the area, register is-last and any areas
            if(islast)
            {
                if(markerLastEnd == null)
                {
                    markerLastEnd = new HashMap();
                }
                // last on page: replace all
                markerLastEnd.putAll(marks);
                if(LogUtil.canDebug())
                {
                	LogUtil.debug("page " + pageNumberString + ": " + "Adding all markers to LastEnd");
                }
            }
            if(markerLastAny == null)
            {
                markerLastAny = new HashMap();
            }
            // last on page: replace all
            markerLastAny.putAll(marks);
            if(LogUtil.canDebug())
            {
            	LogUtil.debug("page " + pageNumberString + ": " + "Adding all markers to LastAny");
            }
        }
    }

    /**
     * Get a marker from this page.
     * This will retrieve a marker with the class name and position.
     * @param name The class name of the marker to retrieve
     * @param pos the position to retrieve
     * @return Object the marker found or null
     */
    public Object getMarker(String name, int pos)
    {
        Object mark = null;
        String posName = null;
        switch(pos)
        {
            case Constants.EN_FSWP:
                if(markerFirstStart != null)
                {
                    mark = markerFirstStart.get(name);
                    posName = "FSWP";
                }
                if(mark == null && markerFirstAny != null)
                {
                    mark = markerFirstAny.get(name);
                    posName = "FirstAny after " + posName;
                }
                break;
            case Constants.EN_FIC:
                if(markerFirstAny != null)
                {
                    mark = markerFirstAny.get(name);
                    posName = "FIC";
                }
                break;
            case Constants.EN_LSWP:
                if(markerLastStart != null)
                {
                    mark = markerLastStart.get(name);
                    posName = "LSWP";
                }
                if(mark == null && markerLastAny != null)
                {
                    mark = markerLastAny.get(name);
                    posName = "LastAny after " + posName;
                }
                break;
            case Constants.EN_LEWP:
                if(markerLastEnd != null)
                {
                    mark = markerLastEnd.get(name);
                    posName = "LEWP";
                }
                if(mark == null && markerLastAny != null)
                {
                    mark = markerLastAny.get(name);
                    posName = "LastAny after " + posName;
                }
                break;
            default:
                throw new RuntimeException();
        }
        if(LogUtil.canDebug())
        {
        	LogUtil.debug("page " + pageNumberString + ": " + "Retrieving marker " + name + " at position " + posName);
        }
        return mark;
    }

    /** Dumps the current marker data to the logger. */
    public void dumpMarkers()
    {
        if(LogUtil.canDebug())
        {
        	LogUtil.debug("FirstAny: " + this.markerFirstAny);
        	LogUtil.debug("FirstStart: " + this.markerFirstStart);
        	LogUtil.debug("LastAny: " + this.markerLastAny);
        	LogUtil.debug("LastEnd: " + this.markerLastEnd);
        	LogUtil.debug("LastStart: " + this.markerLastStart);
        }
    }

    /**
     * Save the page contents to an object stream. The map of unresolved references are set on the page so that
     * the resolvers can be properly serialized and reloaded.
     * @param out the object output stream to write the contents
     * @throws Exception if there is a problem saving the page
     */
    public void savePage(ObjectOutputStream out) throws Exception
    {
        // set the unresolved references so they are serialized
        page.setUnresolvedReferences(unresolvedIDRefs);
        out.writeObject(page);
        page = null;
    }

    /**
     * Load the page contents from an object stream. This loads the page contents from the stream and if there are any
     * unresolved references that were resolved while saved they will be resolved on the page contents.
     * @param in the object input stream to read the page from
     * @throws Exception if there is an error loading the page
     */
    public void loadPage(ObjectInputStream in) throws Exception
    {
        page = (Page)in.readObject();
        unresolvedIDRefs = page.getUnresolvedReferences();
        if(unresolvedIDRefs != null && pendingResolved != null)
        {
            for(Iterator iter = pendingResolved.keySet().iterator();
                                iter.hasNext(); )
            {
                String id = (String)iter.next();
                resolveIDRef(id, (List)pendingResolved.get(id));
            }
            pendingResolved = null;
        }
    }

    /**
     * Clone this page. Used by the page master to create a copy of an original page.
     * @return a copy of this page and associated viewports
     */
    public Object clone()
    {
        PageViewport p = new PageViewport(this);
        p.mediaUsage = this.mediaUsage;
        /* 【添加：START】 by 李晓光  2008-09-09 */
        /*p.textAreas = this.textAreas;*/
        /*p.pageKey = this.pageKey;*/
        p.page = this.page;
        p.setAreaKind(getAreaKind());
        if(containers != null)
        	p.containers = this.containers;
        p.pageIndex = this.pageIndex; 
        /* 【添加：END】 by 李晓光  2008-09-09 */
        /* 【添加：END】 by 李晓光  2009-12-4 */
        p.spm = this.spm;
        if(qianzhangareas!=null)
        {
        	p.qianzhangareas=qianzhangareas;
        }
        /* 【添加：END】 by 李晓光  2009-12-4 */
        return p;
    }

    /**
     * Clear the page contents to save memory.
     * This object is kept for the life of the area tree since it holds id and marker information and is used as a key.
     */
    public void clear()
    {
        page = null;
    }

    /** @see java.lang.Object#toString()    */
    public String toString()
    {
        StringBuffer sb = new StringBuffer(64);
        sb.append("PageViewport: page=");
        sb.append(getPageNumberString());
        return sb.toString();
    }

    /** @return the name of the simple-page-master that created this page */
    public String getSimplePageMasterName()
    {
        return this.simplePageMasterName;
    }

    /**
     * Adds a new ExtensionAttachment instance to this page.
     * @param attachment the ExtensionAttachment
     */
   /* public void addExtensionAttachment(ExtensionAttachment attachment)
    {
        if(this.extensionAttachments == null)
        {
            this.extensionAttachments = new java.util.ArrayList();
        }
        extensionAttachments.add(attachment);
    }*/

    /** @return the list of extension attachments for this page */
    public List getExtensionAttachments()
    {
        if(this.extensionAttachments == null)
        {
            return Collections.EMPTY_LIST;
        }
        else
        {
            return this.extensionAttachments;
        }
    }

    /** @return True if this is a blank page. */
    public boolean isBlank()
    {
        return this.blank;
    }

    /**
     * Convenience method to get BodyRegion of this PageViewport
     * @return BodyRegion object
     */
    public BodyRegion getBodyRegion()
    {
        return(BodyRegion)getPage().getRegionViewport(Constants.FO_REGION_BODY).getRegionReference();
    }

    /**
     * Convenience method to create a new Span for this this PageViewport.
     * @param spanAll whether this is a single-column span
     * @return Span object created
     */
    public Span createSpan(boolean spanAll)
    {
        return getBodyRegion().getMainReference().createSpan(spanAll);
    }

    /**
     * Convenience method to get the span-reference-area currently being processed
     * @return span currently being processed.
     */
    public Span getCurrentSpan()
    {
        return getBodyRegion().getMainReference().getCurrentSpan();
    }

    /**
     * Convenience method to get the normal-flow-reference-area  currently being processed
     * @return span currently being processed.
     */
    public NormalFlow getCurrentFlow()
    {
        return getCurrentSpan().getCurrentFlow();
    }

    /**
     * Convenience method to increment the Span to the  next NormalFlow to be processed, and to return that flow.
     * @return the next NormalFlow in the Span.
     */
    public NormalFlow moveToNextFlow()
    {
        return getCurrentSpan().moveToNextFlow();
    }

    /**
     * Convenience method to return a given region-reference-area, keyed by the Constants class identifier for the
     * corresponding formatting object (ie. Constants.FO_REGION_BODY, FO_REGION_START, etc.)
     * @param id the Constants class identifier for the region.
     * @return the corresponding region-reference-area for this page.
     */
    public RegionReference getRegionReference(int id)
    {
        return getPage().getRegionViewport(id).getRegionReference();
    }
}
